package com.punch.order.design.strategy;


import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSONObject;
import com.punch.common.dto.order.OrderDTO;
import com.punch.common.enums.order.OrderStatusEnum;
import com.punch.common.exception.BusinessException;
import com.punch.common.redis.utils.RedisUtil;
import com.punch.order.asyn.OrderAsync;
import com.punch.order.constants.PayChannelEnum;
import com.punch.order.design.PayStrategy;
import com.punch.order.exception.OrderExceptionEnum;
import com.punch.order.mapper.TimeTaskMapper;
import com.punch.order.model.CommonPayInfo;
import com.punch.order.service.CommonOrderService;
import com.punch.order.service.CommonPayInfoService;
import com.punch.order.wxpay.WXPay;
import com.punch.order.wxpay.config.WxPayConfig;
import com.punch.order.wxpay.constant.WXPayConstants;
import com.punch.order.wxpay.util.WXPayUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Map;
import java.util.Optional;

@Component
@Slf4j
public class WxPayAppStrategy implements PayStrategy {


    @Resource
    private WxPayConfig config;

    @Value("${punch.wx.pay.notifyUrl}")
    private String wxPayNotifyUrl;

    @Resource
    @Lazy
    private OrderAsync orderAsync;

    @Resource
    private CommonOrderService commonOrderService;

    @Resource
    private CommonPayInfoService commonPayInfoService;

    @Resource
    private TimeTaskMapper timeTaskMapper;

    @Resource
    private RedisUtil redisUtil;


    /**
     * 生成统一签名订单信息
     *
     * @param orderDTO 订单信息
     * @return 订单信息
     */
    @Override
    public JSONObject payOrder(OrderDTO orderDTO) throws BusinessException {
        String orderSN = orderDTO.getOrderSN();
        JSONObject jsonObject = new JSONObject();
        Map<String, String> data = MapUtil.newHashMap();
        String ip = orderDTO.getClientIp();
        data.put("body", orderDTO.getBody());
        String outTradeNO = orderDTO.getOrderType() + "-" + orderSN;
        data.put("out_trade_no", outTradeNO);
//            data.put("total_fee", String.valueOf(orderDTO.getTotalAmount()));
        data.put("total_fee", String.valueOf(1));
        data.put("spbill_create_ip", ip); // 下单ip
        data.put("trade_type", "APP"); // 固定填写
        data.put("attach", String.valueOf(orderDTO.getOrderId())); // 固定填写
        log.info("发起微信支付下单接口, request={}", data);
        Map<String, String> resMap;
        try {
            WXPay wxpay = new WXPay(config, wxPayNotifyUrl);
            resMap = wxpay.unifiedOrder(data); // 微信sdk集成方法, 统一下单接口unifiedOrder, 此处请求MD5加密
        } catch (Exception e) {
            log.error("微信统一下单失败:{}", e.getMessage());
            throw new BusinessException(OrderExceptionEnum.CREATE_ORDER_ERROR);
        }
        log.info("微信支付下单成功, 返回值 response={}", resMap);
        String returnCode = resMap.get("return_code");
        if ("FAIL".equals(returnCode)) {
            throw new BusinessException(OrderExceptionEnum.PAY_ERROR_16000);
        }
        String resultCode = resMap.get("result_code");
        if ("FAIL".equals(resultCode)) {
            throw new BusinessException(OrderExceptionEnum.PAY_ERROR_16000);
        }
        //预支付交易会话标识
        String prepayId = resMap.get("prepay_id");
        if (prepayId == null) {
            throw new BusinessException(OrderExceptionEnum.PAY_ERROR_16000);
        }
        jsonObject.put("prepayId", prepayId);
        String timeStamp = String.valueOf(LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8")));
        jsonObject.put("timeStamp", timeStamp);
        String nonceStr = IdUtil.fastSimpleUUID();
        jsonObject.put("nonceStr", nonceStr);
        String sign = generateSign(prepayId, timeStamp, nonceStr);
        jsonObject.put("sign", sign);
        //异步任务将未支付订单加入到动态定时任务
        orderAsync.addUnPayTask(orderDTO.getOrderId(), orderDTO.getCreateTime());
        return jsonObject;
    }

    /**
     * 支付回调
     *
     * @param request  请求
     * @param response 返回
     * @return string
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String payNotify(HttpServletRequest request, HttpServletResponse response) throws BusinessException {
        response.setContentType("text/xml");
        Map<String, String> callBackMap;
        boolean isSignSuccess;
        try {
            String callBackStr = WXPayUtil.readRequest(request);
            log.info("微信支付回调信息:{}", callBackStr);
            callBackMap = WXPayUtil.xmlToMap(callBackStr);
            //验证签名是否正确
            isSignSuccess = WXPayUtil.isSignatureValid(callBackMap, config.getKey(), WXPayConstants.SignType.HMACSHA256);
        } catch (Exception e) {
            log.error("微信支付回调异常错误:{}", e.getMessage());
            return fail();
        }
        if (!isSignSuccess) {
            return fail();
        }
        // 签名校验成功，说明是微信服务器发出的数据
        String outTradeNo = callBackMap.get("out_trade_no");
        // 支付成功
        if ("FAIL".equals(callBackMap.get("return_code")) || "FAIL".equals(callBackMap.get("result_code"))) {
            return fail();
        }
        String[] outTradeNoArr = outTradeNo.split("-");
        Integer orderType = Integer.valueOf(outTradeNoArr[0]);
        String orderSN = outTradeNoArr[1];
        Long orderId = Long.valueOf(callBackMap.get("attach"));
        //订单支付完成
        try {
            commonOrderService.finishPayOrder(orderId);
        } catch (Exception e) {
            return fail();
        }
        //删除未支付的异步任务
        String key = "timeTask:" + orderId;
        Optional.of(redisUtil.getValue(key)).ifPresent((value) -> {
            Long timeTaskId = (Long) value;
            timeTaskMapper.deleteById(timeTaskId);
            commonOrderService.updateOrderStatus(orderId, OrderStatusEnum.HAVE_CANCEL.getCode());
        });
        CommonPayInfo commonPayInfo = new CommonPayInfo();
        commonPayInfo.setOrderType(orderType);
        commonPayInfo.setOrderSN(orderSN);
        commonPayInfo.setOrderId(orderId);
        String paySn = callBackMap.get("transaction_id");
        Integer totalAmount = Integer.parseInt(callBackMap.get("total_fee"));
        commonPayInfo.setAmount(totalAmount);
        //支付时间
        String timeEnd = callBackMap.get("time_end");
        commonPayInfo.setPayTime(timeEnd);
        String payAccount = callBackMap.get("openid");
        commonPayInfo.setPayAccount(payAccount);
        commonPayInfo.setPaySn(paySn);
        String payChannel = PayChannelEnum.WXPAY_APP.getCode() + "_" + callBackMap.get("trade_type");
        commonPayInfo.setPayChannel(payChannel);
        commonPayInfoService.save(commonPayInfo);
        return success();
    }


    /**
     * 生成签名给前端
     *
     * @param prepayId  预支付交易会话标识
     * @param timeStamp 时间戳
     * @param nonceStr  随机字符串
     * @return string
     */
    private String generateSign(String prepayId, String timeStamp, String nonceStr) {
        String packages = "prepay_id=" + prepayId;
        Map<String, String> wxPayMap = MapUtil.newHashMap();
        wxPayMap.put("appId", config.getAppID());
        wxPayMap.put("timeStamp", timeStamp);
        wxPayMap.put("nonceStr", nonceStr);
        wxPayMap.put("package", packages);
        wxPayMap.put("signType", WXPayConstants.HMACSHA256);
        // 加密串中包括 appId timeStamp nonceStr package signType 5个参数, 通过sdk WXPayUtil类加密, 注意, 此处使用  MD5加密  方式
        String sign;
        try {
            sign = WXPayUtil.generateSignature(wxPayMap, config.getKey(), WXPayConstants.SignType.HMACSHA256);
        } catch (Exception e) {
            log.error("微信统一下单失败:{}", e.getMessage());
            throw new BusinessException(OrderExceptionEnum.GENERATE_WX_SIGN_ERROR);
        }
        return sign;
    }

    private String fail() {
        return "<xml>\n" +
                "  <return_code><![CDATA[FAIL]]></return_code>\n" +
                "  <return_msg><![CDATA[]]></return_msg>\n" +
                "</xml>";
    }

    private String success() {
        return "<xml>\n" +
                "  <return_code><![CDATA[SUCCESS]]></return_code>\n" +
                "  <return_msg><![CDATA[OK]]></return_msg>\n" +
                "</xml>";
    }
}
